﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

using Vector3 = DDD.Vector3<float>;

namespace DDD {
    /// <summary>
    /// 3D空間上に存在するオブジェクトを表現する抽象クラス
    /// </summary>
    /// <remarks>
    /// シーンに存在する全ノードはこの<see cref="Object3D"/>を継承します。
    /// 主に座標変換を担当します。
    /// <para>
    /// 座標変換
    /// DDDでは座標変換をT(Translate:平行移動), R(Orietation:回転), S(Scale:拡大), M(Matrix:任意の4x4行列)の4つで表します。
    /// それぞれの要素(TValue,R,S,M)は別々に保存され操作されます。
    /// 最終的な変換行列はTRSMを全てこの順でかけた行列です(<code>V' = TRSM･V</code>)。
    /// 従ってまずM要素が適用され、次にS要素、R要素、最後にT要素の順に適用されます。
    /// このように変換行列を管理することでユーザーは直感的に物体を操作することが可能です。
    /// </para>
    /// </remarks>
    public abstract class Object3D : Object {

        #region Field
        float[] translation;
        Quaternion orientation;
        float[] scaling;
        Matrix4x4 matrix;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public Object3D () {
            this.translation = new float[3] { 0, 0, 0 };
            this.orientation = Quaternion.Identity;
            this.scaling = new float[3] { 1, 1, 1 };
            this.matrix = Matrix4x4.Identity;
        }
        #endregion


        #region Property

        /// <summary>
        /// TRSM行列
        /// </summary>
        /// <remarks>
        /// このノードのローカル座標系から１つ上の親の座標系に変換する行列です。
        /// <code>Matrix = TRSM</code>で計算されます。
        /// </remarks>
        public Matrix4x4 TRSM {
            get {
                var t = Matrix4x4.Translate (translation[0], translation[1], translation[2]);
                var r = Matrix4x4.Rotate (orientation);
                var s = Matrix4x4.Scale (scaling[0], scaling[1], scaling[2]);
                var m = matrix;
                return t * r * s * m;
            }
        }

        /// <summary>
        /// 平行移動成分T
        /// </summary>
        public Vector3 Translation {
            get { return new Vector3 (translation[0], translation[1], translation[2]); }
            private set {
                this.translation[0] = value[0];
                this.translation[1] = value[1];
                this.translation[2] = value[2];
            }
        }

        /// <summary>
        /// 回転成分R
        /// </summary>
        public Quaternion Orientation {
            get { return orientation; }
            private set {
                this.orientation[0] = value[0];
                this.orientation[1] = value[1];
                this.orientation[2] = value[2];
                this.orientation[3] = value[3];
            }
        }

        /// <summary>
        /// 拡大縮小成分S
        /// </summary>
        public Vector3 Scaling {
            get { return new Vector3 (scaling[0], scaling[1], scaling[2]); }
            private set {
                this.scaling[0] = value[0];
                this.scaling[1] = value[1];
                this.scaling[2] = value[2];
            }
        }

        /// <summary>
        /// 行列成分M
        /// </summary>
        public Matrix4x4 Matrix {
            get { return matrix; }
            private set { this.matrix = value; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }

        #endregion


        #region Method
        /// <summary>
        /// 平行移動します
        /// </summary>
        /// <remarks>
        /// 現在のT要素に新たな平行移動量を加算します。
        /// </remarks>
        /// <param name="tx">平行移動のX成分</param>
        /// <param name="ty">平行移動のY成分</param>
        /// <param name="tz">平行移動のZ成分</param>
        public void Translate (float tx, float ty, float tz) {
            this.translation[0] += tx;
            this.translation[1] += ty;
            this.translation[2] += tz;
        }

        /// <summary>
        /// 現在のT要素を破棄して新たな平行移動量をセットします。
        /// </summary>
        /// <param name="tx">平行移動のX成分</param>
        /// <param name="ty">平行移動のX成分</param>
        /// <param name="tz">平行移動のX成分</param>
        public void SetTranslation (float tx, float ty, float tz) {
            this.translation[0] = tx;
            this.translation[1] = ty;
            this.translation[2] = tz;
        }

        /// <summary>
        /// 現在のR要素に後ろに新たな回転を付け加えます。
        /// </summary>
        /// <remarks>
        /// 回転角は度数(in degree[0,360))で指定します。
        /// 回転軸は正規化されている必要はありません。
        /// </remarks>
        /// <param name="angle">回転角(in degree)</param>
        /// <param name="ax">回転軸のX成分</param>
        /// <param name="ay">回転軸のY成分</param>
        /// <param name="az">回転軸のZ成分</param>
        public void Rotate (float angle, float ax, float ay, float az) {
            var q = new Quaternion (angle, ax, ay, az);
            this.orientation = q * orientation;
        }

        /// <summary>
        /// 現在のR要素に後ろに新たな回転を付け加えます。
        /// </summary>
        /// <remarks>
        /// 回転はクォータニオンで指定します。
        /// </remarks>
        /// <param name="q">クォータニオン</param>
        public void Rotate (Quaternion q) {
            this.orientation = q * orientation;
        }

        /// <summary>
        /// 現在のR要素を破棄し新たな回転をセットします。
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="angle">回転角度(in degree)</param>
        /// <param name="ax">回転軸のX成分</param>
        /// <param name="ay">回転軸のY成分</param>
        /// <param name="az">回転軸のZ成分</param>
        public void SetOrientation (float angle, float ax, float ay, float az) {
            this.orientation = new Quaternion (angle, ax, ay, az);
        }

        /// <summary>
        /// 現在のR要素を破棄し新たな回転をセットします。
        /// </summary>
        /// <remarks>
        /// 回転はクォータニオンで指定します。
        /// </remarks>
        /// <param name="q">クォータニオン</param>
        public void SetOrientation (Quaternion q) {
            this.orientation = q;
        }


        /// <summary>
        /// 現在のS要素に指定の拡大･縮小を積算します。
        /// </summary>
        /// <param name="sx">拡大量のX成分</param>
        /// <param name="sy">拡大量のY成分</param>
        /// <param name="sz">拡大量のZ成分</param>
        public void Scale (float sx, float sy, float sz) {

            this.scaling[0] *= sx;
            this.scaling[1] *= sy;
            this.scaling[2] *= sz;
        }

        /// <summary>
        /// 現在のS要素を破棄し新たな拡大縮小をセットします
        /// </summary>
        /// <param name="sx">拡大量のX成分</param>
        /// <param name="sy">拡大量のY成分</param>
        /// <param name="sz">拡大量のZ成分</param>
        public void SetScaling (float sx, float sy, float sz) {
            this.scaling[0] = sx;
            this.scaling[1] = sy;
            this.scaling[2] = sz;
        }

        /// <summary>
        /// 現在のM要素を破棄し新たな行列をセットします
        /// </summary>
        /// <remarks>
        /// M要素は必ず以前の値を破棄し新たにセットし直します。
        /// M要素の意味とその計算は全てユーザーにまかされています。
        /// </remarks>
        /// <param name="m">行列</param>
        public void SetMatrix (Matrix4x4 m) {
            this.matrix = m;
        }

        /// <summary>
        /// 現在のTR要素を破棄し、指定の位置･方向を向くようにTR要素をセットし直します
        /// </summary>
        /// <remarks>
        /// TR要素を破棄し位置<code>(ox,oy,oz)</code>から注視点<code>(dx,dy,dz)</code>、Upベクトル<code>(upx,upy,upz)</code>を
        /// 向くようにTR要素をセットし直します。
        /// この関数は<code>gluLookAt()</code>に相当します。
        /// /// </remarks>
        /// <param name="ox">位置のX座標</param>
        /// <param name="oy">位置のY座標</param>
        /// <param name="oz">位置のZ座標</param>
        /// <param name="dx">注視点のX成分</param>
        /// <param name="dy">注視点のY成分</param>
        /// <param name="dz">注視点のZ成分</param>
        /// <param name="upx">UpベクトルのX成分</param>
        /// <param name="upy">UpベクトルのY成分</param>
        /// <param name="upz">UpベクトルのZ成分</param>
        public void LookAt (float ox, float oy, float oz, float dx, float dy, float dz, float upx, float upy, float upz) {
            var dir = new Vector3 (dx - ox, dy - oy, dz - oz).Normalize ();
            var up = new Vector3 (upx, upy, upz).Normalize ();
            var right = Vector3.Cross (dir, up);
            var up2 = Vector3.Cross (right, dir);
            var rot = new Matrix3x3 (right[0], up2[0], -dir[0],
                                     right[1], up2[1], -dir[1],
                                     right[2], up2[2], -dir[2]);
            this.translation[0] = ox;
            this.translation[1] = oy;
            this.translation[2] = oz;
            this.orientation = new Quaternion (rot);
        }


        /// <summary>
        /// 整列(アライン)処理を行います
        /// </summary>
        /// <remarks>
        /// 事前に設定されたアライメント設定に応じてこのノードのTR要素を変更します。
        /// S要素とM要素はそのまま維持します。
        /// このノードのみに作用します
        /// </remarks>
        public void Align () {
            throw new NotImplementedException ("Sorry");
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("Object3D: T={0}, R={1}, S={2}, M={3}", translation, orientation, scaling, matrix);
        }
        #endregion

    }
}
